#include <fstream>
#include <vector>
#include <iostream>
#include <string>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
#include <map>

using namespace std;

struct Color
{
	unsigned char A, R, G, B;

	Color(void)
	{
		memset(this, 0, sizeof(Color));
	}
};

//Compress color data
void CompressData(Color* in_data, char** out_data, int bufferSize, int* out_bufferSize);

// use to pause and wait for user input
void Pause(void);

// get the parent directory of a directory
void GetParentDir(char* directoryName);

int main(int argc, char** argv)
{
	// The program fails if no parameters are passed to it
	if (argc == 1)
	{
		cout << "No arguments were passed, compression failed";
		Pause();
		return -1;
	}

	// get the filename out of command-line arguments
	char* currentDir = argv[0];
	PathRemoveFileSpec(currentDir);
	GetParentDir(currentDir);
	string filename = string(currentDir) + "\\" + string(argv[1]);

	// read in pixel data
	fstream file(filename, ios::binary | ios::in);
	// ensure the file has been opened
	if (!file.is_open())
	{
		cout << "File failed to open, compression failed" << endl;
		Pause();
		return -1;
	}

	int width, height;
	file.read(reinterpret_cast<char*>(&width), sizeof(int));
	file.read(reinterpret_cast<char*>(&height), sizeof(int));

	Color* pixel_inData = new Color[width * height];
	file.read(reinterpret_cast<char*>(pixel_inData), sizeof(Color) * width * height);
	// close the file
	file.close();

	// compress image data
	char* pixel_outData;
	int out_bufferSize;
	CompressData(pixel_inData, &pixel_outData, width * height, &out_bufferSize);

	// save compressed data to disk
	fstream out_file(filename, ios::binary | ios::out);
	out_file.write(pixel_outData, out_bufferSize);

	// relese memory
	delete pixel_inData;
	delete pixel_outData;
	Pause();
	return 0;
}


void CompressData(Color* in_data, char** out_data, int bufferSize, int* out_bufferSize)
{
	// the duplacate bytes
	vector<char> duplacates;
	// the indicies of all bytes
	vector<unsigned int> indicies;

	// ammount to increace, per processed channel
	float percentIncreace_step = 1 / float(bufferSize);
	float percentComplete = 0.f;
	
	// convert the in-buffer to an array of color channels
	char* data = reinterpret_cast<char*>(in_data);
	// the length of the in buffer in ints
	int bufferSize_byte = bufferSize * 4;
	for (int i = 0; i < bufferSize_byte; i++)
	{
		bool foundValue = false;
		// check to see if this channel already exists
		for (unsigned int ii = 0; ii < duplacates.size(); ii++)
		{
			// if it already exists, add this index to the list of duplicate indicies
			if (duplacates[ii] == *(data + i))
			{
				indicies.push_back(i);
				foundValue = true;
				break;
			}
		}

		if (!foundValue)
		{
			// if the value was not in the list of duplacates, add a new value to duplacates
			duplacates.push_back(*(data + i));
			// add the index of this value as well
			indicies.push_back(i);
		}

		// incrament percent complete
		percentComplete += percentIncreace_step;
		// indicate the current progress
		float progress = i / (float)bufferSize_byte;
		cout << progress << "%\n";
	}

	// compute the size of the new buffer
	*out_bufferSize = duplacates.size() * sizeof(int) + indicies.size() * sizeof(int);
	// create output buffer
	*out_data = new char[*out_bufferSize];
	// copy in duplacate dictionary
	memcpy(*out_data, &duplacates[0], duplacates.size() * sizeof(int));
	// copy in index data
	memcpy(*out_data + duplacates.size() * sizeof(int), &indicies[0], indicies.size() * sizeof(int));
}

void Pause(void)
{
	cout << "Press any key to continue\n";
	cin.get();
}

void GetParentDir(char* directoryName)
{
	// find the current directory name
	int length = strlen(directoryName) - 1;
	char* currentChar = directoryName + length;
	while (*currentChar != '\\')
	{
		currentChar--;
		// if the entire string has been checked, break out of the function and show the user
		// an error message
		if (currentChar == directoryName)
		{
			cout << "Error: invalid file path provided\n";
			return;
		}
	}
	
	// make sure there's not another backslash in the directory name
	if (*(currentChar - 1) == '\\')
		currentChar--;

	// set the end of the string to the current char
	*currentChar = '\0';
}